﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using tuwien.auto.eibclient;
using System.Threading;
using KNXFramework.Logging;
using KNXLibrary.Addresses;

namespace KNXFramework
{
    public class KNXConnection
    {
        #region Fields
        private string m_HostName;
        private int m_Port;
        private EIBConnection m_Connection = null;
        private Thread m_GroupSocketThread = null;
        private bool m_IsRunning = false;


        #endregion

        #region Properties
        public string HostName
        {
            get { return m_HostName; }
            set { m_HostName = value; }
        }
        public int Port
        {
            get { return m_Port; }
            set { m_Port = value; }
        }
        public bool IsRunning
        {
            get
            {
                return m_IsRunning;
            }
        }
        #endregion

        public delegate void KNXDeletage(PhysicalAddress src, GroupAddress dest, EIBBuffer buffer);
        public event KNXDeletage OnRead;
        public event KNXDeletage OnResponse;
        public event KNXDeletage OnWrite;
        private AutoResetEvent resetEvent;
        private ManualResetEvent CheckInputResetEvent = new ManualResetEvent(false);
        private static bool s_ReadPending = false;

        public static bool ReadPending
        {
            get { return KNXConnection.s_ReadPending; }
            set { KNXConnection.s_ReadPending = value; }
        }
        private static object s_ReadLockObject = new object();
        #region Constructors
        public KNXConnection(string hostName, int port)
        {
            m_HostName = hostName;
            m_Port = port;

        }
        #endregion

        #region Methods

        public void Start()
        {
            CustomLogger.WriteInformationEntry("Starting connection...");
            m_Connection = new EIBConnection(HostName, Port);
            m_IsRunning = true;
            CustomLogger.WriteInformationEntry("Connection started");

            StartListening();
        }
        public void StartListening()
        {
            m_GroupSocketThread = new Thread(new ParameterizedThreadStart(OpenGroupSocket));
            resetEvent = new AutoResetEvent(false);
            m_GroupSocketThread.Name = "KNX Thread";
            CustomLogger.WriteInformationEntry("Starting group socket thread");
            m_GroupSocketThread.Start(m_Connection);
            resetEvent.WaitOne(10000, true);
            CustomLogger.WriteInformationEntry("Group socket thread initialized");

        }
        public void Write(GroupAddress ga, EIBBuffer buffer)
        {
            if (ga.Value == 0)
                return;
            if (m_Connection != null)
            {
                CustomLogger.WriteVerboseEntry(string.Format("write gad: {0}, buf: {1}", ga, buffer));
                int len = m_Connection.EIBSendGroup(ga.Value, buffer.data);
                if (len == -1)
                    throw new ApplicationException("Request failed");

                
            }
        }
        public void Read(GroupAddress ga)
        {            
            lock (s_ReadLockObject)
            {
                if (!ReadPending)
                {
                    bool v = CheckInputResetEvent.Reset();
                    EIBBuffer buffer = new EIBBuffer(new byte[] { 0, 0 });
                    ReadPending = true;
                    Write(ga, buffer);
                    ReadPending = false;
                    CheckInputResetEvent.WaitOne(1000,true);
                }
                else
                {
                    throw new ApplicationException("Read pending");
                }
            }
        }
        public void CloseConnection()
        {
            if (IsRunning)
            {
                CustomLogger.WriteInformationEntry("Stopping connection");
                m_GroupSocketThread.Abort();
                m_Connection.EIBClose();
                CustomLogger.WriteInformationEntry("Connection stopped");
            }
        }
        private void OpenGroupSocket(object obj)
        {

            EIBConnection connection = obj as EIBConnection;
            if (connection != null)
            {
                CustomLogger.WriteVerboseEntry("Opening group socket...");
                if (connection.EIBOpen_GroupSocket(false) != -1)
                {
                    CustomLogger.WriteVerboseEntry("Group socket opened. Waiting for messages");
                    int retval;
                    CustomLogger.WriteVerboseEntry("Set reset event from group socket thread");
                    resetEvent.Set();
                    while ((retval = CheckInput()) > 0) { }
                }
                else
                    CustomLogger.WriteCriticalEntry("Failed to open group socket", KNXLibrary.Enum.Priority.High);
            }
        }
        public int CheckInput()
        {            
            int len;
            EIBAddr dest = new EIBAddr();
            EIBAddr src = new EIBAddr();            
            EIBBuffer buf = new EIBBuffer();
            CustomLogger.WriteVerboseEntry("Check input");
            len =  m_Connection.EIBGetGroup_Src(buf, src, dest);
            if (len == -1)
            {
                CustomLogger.WriteErrorEntry("Read failed", KNXLibrary.Enum.Priority.High);
                return len;
                
            }
            if (len < 2)
            {
                CustomLogger.WriteErrorEntry("Invalid Packet", KNXLibrary.Enum.Priority.High);
            }
            PhysicalAddress physSrc = new PhysicalAddress(src.addr);
            GroupAddress gaDest = new GroupAddress(dest.addr);
            if ((buf.data[0] & 0x3) == 0x3 || (buf.data[1] & 0xc0) == 0xc0)
            {
                CustomLogger.WriteErrorEntry(string.Format("Unknown APDU from {0} to {1}", physSrc, gaDest));
            }
            else
            {
                string message = "{0} from {1} to {2}: {3}";
                KNXDeletage handler = null;
                bool resetEvent = false;
                switch (buf.data[1] & 0xc0)
                {
                    case 0x00:                        
                        message = string.Format(message, "Read", physSrc, gaDest, (buf.data[1] & 0x3F).ToString("X2"));
                        handler = OnRead as KNXDeletage;
                        break;
                    case 0x40:                        
                        message = string.Format(message, "Response", physSrc, gaDest, (buf.data[1] & 0x3F).ToString("X2"));
                        handler = OnResponse as KNXDeletage;
                        ReadPending = false;
                        resetEvent = true;
                        break;
                    case 0x80:                        
                        message = string.Format(message, "Write", physSrc, gaDest, (buf.data[1] & 0x3F).ToString("X2"));
                        handler = OnWrite as KNXDeletage;
                        break;
                }
                if (handler != null)
                {
                    CustomLogger.WriteVerboseEntry(message);
                    handler(physSrc, gaDest, buf);
                    if (resetEvent)
                        CheckInputResetEvent.Set();
                }

            }
            return 1;

        }
        #endregion
    }
}
